#include "mainwidget.h"
#include <QFileDialog>
#include <QFileInfo>
#include <QMouseEvent>
#include <QTextStream>
#include <cfloat>
#include <sys/time.h>
#include "../dataengine/TxtTerrainFileManager.h"
#include "../dataengine/ShpTerrainFileManager.h"
#include "../dataengine/VibTerrainFileManager.h"
#include "voronoi/VoronoiRenderer.h"
#include "ui/triangulationprogessdialog.h"

MainWidget::MainWidget(QWidget * parentwidget) :  QGLWidget(parentwidget), m_camera(geom::Point3D(0,30,6),geom::Vector3D(0,-1,0),90)
{
    m_wireframeTerrain = false;
    voronoiCellSize = 2.0;
    voronoiMaxDensity = 1.0;
    depthDivisor = 1.0;
    m_renderEnabled = true;
    this->setAutoBufferSwap(false);
}

void MainWidget::initializeGL()
{
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_DEPTH_TEST);
}

void MainWidget::resizeGL(int w, int h)
{
    makeCurrent();
    glViewport(0,0,w,h);
    m_camera.setAspectRatio(w/(float)h);
    doneCurrent();
}

void MainWidget::paintGL()
{
    if(m_renderEnabled == false)
        return;
    makeCurrent();
	glUseProgram(0);
	glClearColor(0.7,0.7,1.0,0.0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    m_camera.lookAt(displayCenter);
    m_camera.setAsGLCamera();
    {
        //glScalef(displayScale.dx,displayScale.dy, displayScale.dz);
        if(m_currentTerrainRenderer.get()!=NULL)
        {
            if(m_wireframeTerrain)
                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
            setGLLight();
            m_currentTerrainRenderer->render();
            if(m_wireframeTerrain)
                glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);         
        }
    }
    this->swapBuffers();
    doneCurrent();
}

void MainWidget::setGLLight()
{
    float ambientLight0[4] = {0.2,0.2,0.2,1.0f};
    float diffuseLight0[4] = {0.5,0.5,0.5,1.0f};
    float specularLight0[4] = {0.0,0.0,0.0,1.0f};
    float lightPosition0 [4] = {20, 30, 40,0.0};

    glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight0);
    glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight0);
    glLightfv(GL_LIGHT0,GL_SPECULAR,specularLight0);
    glLightfv(GL_LIGHT0,GL_POSITION,lightPosition0);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

void MainWidget::doOpenFile()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open Terrain File"),
                                                    ".",
                                                    tr("ViBos Terrain Files (*.vibt);;All Files(*.*)"));
    if(fileName != "")
    {
        dataengine::VibTerrainFileManager fileMan(fileName.toStdString());
        if(!fileMan.canLoadFile())
            return;
        m_currentModel = fileMan.loadFile();
        m_currentModel.setBaseColor(0.5,1.0,0.5);

        m_currentTerrainRenderer.reset(new glutil::TerrainRenderer(&m_currentModel));
        if(fileName.size() > 30)
            fileName = "..."+fileName.right(27);
        emit fileChanged(fileName);

     //   updatePointsBBox();
        updateGL();
    }

}
void MainWidget::doImportFile()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Import Terrain File"),
                                                    ".",
                                                    tr("Shape Files (*.shx);;TXT Files (*.txt)"));
    if(fileName != "")
    {
        m_currentTerrainRenderer.reset(NULL);
        QFileInfo finfo(fileName);
        if(finfo.suffix() == "shx")
        {
            dataengine::ShpTerrainFileManager shpFileManager(fileName.toStdString());
            if(!shpFileManager.canLoadFile())
                return; // TODO show error to the user
            m_currentModel = shpFileManager.loadFile();
        }
        else if(finfo.suffix() == "txt")
        {
            dataengine::TxtTerrainFileManager txtFileManager(fileName.toStdString(), " ,");
            if(!txtFileManager.canLoadFile())
                return; // TODO show error to the user
            m_currentModel = txtFileManager.loadFile();
        }
        else
            return; // TODO show error to the user

        if(fileName.size() > 30)
            fileName = "..."+fileName.right(27);
        emit fileChanged(fileName);
        updatePointsBBox();
        m_currentTerrainRenderer.reset(new glutil::TerrainRenderer(&m_currentModel));
        updateGL();
    }

}
void MainWidget::updateDisplayScale()
{
    for(int i=0;i<3;++i)
        displayScale.dv[i] = desiredTerrainSize.dv[i] / m_currentModelBBox.sideSize(i);
}

void MainWidget::updatePointsBBox()
{
	/*
    dataengine::PointsList::const_iterator it = m_currentModelPoints.begin();
    dataengine::PointsList::const_iterator end = m_currentModelPoints.end();
    geom::Point3D minPoint(FLT_MAX,FLT_MAX,FLT_MAX);
    geom::Point3D maxPoint(FLT_MIN,FLT_MIN,FLT_MIN);
    for(;it!=end;++it)
        for(int i=0;i<3;++i)
        {
            if((*it).v[i] < minPoint.v[i])
                minPoint.v[i] = (*it).v[i];
            if((*it).v[i] > maxPoint.v[i])
                maxPoint.v[i] = (*it).v[i];
        }

    m_currentModelBBox= geom::FittedBBox(minPoint,maxPoint);
    displayScale=geom::Vector3D(1.0,1.0,1.0);
    for(int i=0;i<3;++i)
        desiredTerrainSize.dv[i] = m_currentModelBBox.sideSize(i);
    emit terrainDimensionsChanged(m_currentModelBBox.sideSize(0), m_currentModelBBox.sideSize(2), m_currentModelBBox.sideSize(1));
	*/
}

void MainWidget::doSaveFile()
{
    if(m_currentModel.getNumberOfPoints() == 0)
        return;
    QString fileName = QFileDialog::getSaveFileName(this,tr("Save as file"),".",tr("ViBos Terrain Files (*.vibt)"));
    if(fileName != "")
    {
        QFileInfo finfo(fileName);
        if(finfo.completeSuffix() != "vibt")
            fileName +=".vibt";

        dataengine::VibTerrainFileManager fileMan(fileName.toStdString());
        fileMan.saveToFile(m_currentModel);
    }
}

void MainWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
}


void MainWidget::mouseMoveEvent(QMouseEvent * event)
{
    m_camera.strafeLeft((event->x() - lastPos.x())/30.0);
    m_camera.moveUp((event->y()-lastPos.y())/-30.0);
   // rotx += (event->x() - lastPos.x())/100.0;
    //roty += (event->y() - lastPos.y())/100.0;
    lastPos = event->pos();

    updateGL();
}
void MainWidget::wheelEvent(QWheelEvent * event)
{
    m_camera.advance(event->delta()/1000.0);
    updateGL();
}

struct timeval firstm,  secondm,  lapsedm;

void MainWidget::doTriangulate()
{
    m_renderEnabled = false;

    this->disableRender();
    m_currentTerrainRenderer.reset(NULL);
    if(m_currentModel.getNumberOfPoints() < 3)
        return;

    makeCurrent();
    voronoi::VoronoiRenderer * vrenderer = new voronoi::VoronoiRenderer(this->m_currentModel.points(),2048,2048);

    VoronoiGenerationThread * vorThread = new VoronoiGenerationThread(const_cast<MainWidget*>(this),vrenderer,voronoiCellSize/10.0,depthDivisor, voronoiMaxDensity);

    TriangulationProgessDialog * dialog = new TriangulationProgessDialog(this);
    connect(dialog,SIGNAL(processCanceled()),vorThread,SLOT(tryToEnd()));
    connect(vorThread,SIGNAL(statusUpdate(float,int,int)),dialog,SLOT(setCompletenessStatus(float,int,int)));
    connect(vorThread,SIGNAL(finished()),dialog,SLOT(close()));

   // this->doneCurrent();

    doneCurrent();
    printf("\n\nCurrent error at doTriangulate after signals: %i\n\n",glGetError());
    vorThread->start();

    gettimeofday (&firstm, NULL);
    printf("\n\nCurrent error at doTriangulate after thread start: %i\n\n",glGetError());

    dialog->exec();
    m_renderEnabled = true;
    this->enableRender();

}

void MainWidget::retrieveTriangulated(voronoi::VoronoiRenderer * vrenderer)
{
    gettimeofday (&secondm, NULL);
    if (firstm.tv_usec > secondm.tv_usec)
    {
        secondm.tv_usec += 1000000;
        secondm.tv_sec--;
    }
    lapsedm.tv_usec = secondm.tv_usec - firstm.tv_usec;
    lapsedm.tv_sec = secondm.tv_sec - firstm.tv_sec;
    printf("Started triangulation after:%li secs, %li microseconds \n",lapsedm.tv_sec,lapsedm.tv_usec); //print lapsed time in microseconds

    std::vector< geom::IntTri > tris;
    std::map<unsigned int, geom::Vector3D> normals;
    m_currentModel.clearTriangles();

    vrenderer->getTriangulated(&tris, &normals);
/*
    std::map<unsigned int, geom::Vector3D>::iterator normIt;
    std::map<unsigned int, geom::Vector3D>::iterator normEnd = normals.end();
    for(normIt = normals.begin();normIt!=normEnd;++normIt)
        terrain.setPointNormal(normIt->first,normIt->second);
*/

       gettimeofday (&secondm, NULL);
    if (firstm.tv_usec > secondm.tv_usec)
    {
        secondm.tv_usec += 1000000;
        secondm.tv_sec--;
    }
    lapsedm.tv_usec = secondm.tv_usec - firstm.tv_usec;
    lapsedm.tv_sec = secondm.tv_sec - firstm.tv_sec;
    printf("Finished triangulation after:%li secs, %i microseconds \n",lapsedm.tv_sec,lapsedm.tv_usec); //print lapsed time in microseconds


    int numTris = tris.size();
    printf("NUM TRIS IN TRIANGULATED TERRAIN: %i\n",numTris);
    util::Triplet<int> triToAdd;
    for(int i=0;i<numTris;++i)
    {
        m_currentModel.addTriangle(tris[i]);
    }

    m_currentModel.setBaseColor(0.5,1.0,0.5);
    m_currentModel.generateNormals();
    m_currentTerrainRenderer.reset(new glutil::TerrainRenderer(&m_currentModel));
    updateGL();
}

void MainWidget::setWireframeTerrain(bool set)
{
    m_wireframeTerrain = set;
    updateGL();
}

void MainWidget::newTerrainDimensions(float width,float length, float height)
{
    desiredTerrainSize = geom::Vector3D(width,height,length);
    updateDisplayScale();
    updateGL();
}

void MainWidget::setVoronoiCellSize(double cellSize)
{
    voronoiCellSize = cellSize;
}

void MainWidget::setVoronoiDepthDivisor(double divisor)
{
    depthDivisor = divisor;
}

void MainWidget::setVoronoiMaxDensity(double density)
{
    voronoiMaxDensity = density;
}

void MainWidget::disableRender()
{
    m_renderEnabled = false;
}

void MainWidget::enableRender()
{
    m_renderEnabled = true;
}

